/**
* Copyright (c) By zengqh.
*
* This program is just for fun or demo, in the hope that it  
* will be useful, you can redistribute it and/or modify freely.
*
* Time: 2012/11/07
* File: EnnDataStream.cpp
* Blog: http://www.cnblogs.com/zengqh/
**/

#include "EnnDataStream.h"

#ifdef min
#undef min
#endif

#ifdef max
#undef max
#endif

namespace Enn
{
	//-----------------------------------------------------------------------
	//-----------------------------------------------------------------------
	template <typename T> DataStream& DataStream::operator >>(T& val)
	{
		read(static_cast<void*>(&val), sizeof(T));
		return *this;
	}
	//-----------------------------------------------------------------------
	String DataStream::getLine(bool trimAfter)
	{
		char tmpBuf[ENN_STREAM_TEMP_SIZE];
		String retString;
		size_t readCount;
		// Keep looping while not hitting delimiter
		while ((readCount = read(tmpBuf, ENN_STREAM_TEMP_SIZE-1)) != 0)
		{
			// Terminate string
			tmpBuf[readCount] = '\0';

			char* p = strchr(tmpBuf, '\n');
			if (p != 0)
			{
				// Reposition backwards
				skip((long)(p + 1 - tmpBuf - readCount));
				*p = '\0';
			}

			retString += tmpBuf;

			if (p != 0)
			{
				// Trim off trailing CR if this was a CR/LF entry
				if (retString.length() && retString[retString.length()-1] == '\r')
				{
					retString.erase(retString.length()-1, 1);
				}

				// Found terminator, break out
				break;
			}
		}

		if (trimAfter)
		{
			StringUtil::trim(retString);
		}

		return retString;
	}
	//-----------------------------------------------------------------------
	size_t DataStream::readLine(char* buf, size_t maxCount, const String& delim)
	{
		// Deal with both Unix & Windows LFs
		bool trimCR = false;
		if (delim.find_first_of('\n') != String::npos)
		{
			trimCR = true;
		}

		char tmpBuf[ENN_STREAM_TEMP_SIZE];
		size_t chunkSize = std::min(maxCount, (size_t)ENN_STREAM_TEMP_SIZE-1);
		size_t totalCount = 0;
		size_t readCount; 
		while (chunkSize && (readCount = read(tmpBuf, chunkSize)) != 0)
		{
			// Terminate
			tmpBuf[readCount] = '\0';

			// Find first delimiter
			size_t pos = strcspn(tmpBuf, delim.c_str());

			if (pos < readCount)
			{
				// Found terminator, reposition backwards
				skip((long)(pos + 1 - readCount));
			}

			// Are we genuinely copying?
			if (buf)
			{
				memcpy(buf+totalCount, tmpBuf, pos);
			}
			totalCount += pos;

			if (pos < readCount)
			{
				// Trim off trailing CR if this was a CR/LF entry
				if (trimCR && totalCount && buf && buf[totalCount-1] == '\r')
				{
					--totalCount;
				}

				// Found terminator, break out
				break;
			}

			// Adjust chunkSize for next time
			chunkSize = std::min(maxCount-totalCount, (size_t)ENN_STREAM_TEMP_SIZE-1);
		}

		// Terminate
		if(buf)
			buf[totalCount] = '\0';

		return totalCount;
	}
	//-----------------------------------------------------------------------
	size_t DataStream::skipLine(const String& delim)
	{
		char tmpBuf[ENN_STREAM_TEMP_SIZE];
		size_t total = 0;
		size_t readCount;
		// Keep looping while not hitting delimiter
		while ((readCount = read(tmpBuf, ENN_STREAM_TEMP_SIZE-1)) != 0)
		{
			// Terminate string
			tmpBuf[readCount] = '\0';

			// Find first delimiter
			size_t pos = strcspn(tmpBuf, delim.c_str());

			if (pos < readCount)
			{
				// Found terminator, reposition backwards
				skip((long)(pos + 1 - readCount));

				total += pos + 1;

				// break out
				break;
			}

			total += readCount;
		}

		return total;
	}
	//-----------------------------------------------------------------------
	String DataStream::getAsString(void)
	{
		// Read the entire buffer - ideally in one read, but if the size of
		// the buffer is unknown, do multiple fixed size reads.
		size_t bufSize = (mSize > 0 ? mSize : 4096);
		char* pBuf = ENN_ALLOC_T(char, bufSize, MEMCATEGORY_GENERAL);
		// Ensure read from begin of stream
		seek(0);
		String result;
		while (!eof())
		{
			size_t nr = read(pBuf, bufSize);
			result.append(pBuf, nr);
		}
		ENN_FREE(pBuf, MEMCATEGORY_GENERAL);
		return result;
	}
	//-----------------------------------------------------------------------
	//-----------------------------------------------------------------------
	MemoryDataStream::MemoryDataStream(void* pMem, size_t inSize, bool freeOnClose, bool readOnly)
		: DataStream(static_cast<uint16>(readOnly ? READ : (READ | WRITE)))
	{
		mData = mPos = static_cast<uchar*>(pMem);
		mSize = inSize;
		mEnd = mData + mSize;
		mFreeOnClose = freeOnClose;
		ENN_ASSERT(mEnd >= mPos);
	}
	//-----------------------------------------------------------------------
	MemoryDataStream::MemoryDataStream(const String& name, void* pMem, size_t inSize, 
		bool freeOnClose, bool readOnly)
		: DataStream(name, static_cast<uint16>(readOnly ? READ : (READ | WRITE)))
	{
		mData = mPos = static_cast<uchar*>(pMem);
		mSize = inSize;
		mEnd = mData + mSize;
		mFreeOnClose = freeOnClose;
		ENN_ASSERT(mEnd >= mPos);
	}
	//-----------------------------------------------------------------------
	MemoryDataStream::MemoryDataStream(DataStream& sourceStream, 
		bool freeOnClose, bool readOnly)
		: DataStream(static_cast<uint16>(readOnly ? READ : (READ | WRITE)))
	{
		// Copy data from incoming stream
		mSize = sourceStream.size();
		if (mSize == 0 && !sourceStream.eof())
		{
			// size of source is unknown, read all of it into memory
			String contents = sourceStream.getAsString();
			mSize = contents.size();
			mData = ENN_ALLOC_T(uchar, mSize, MEMCATEGORY_GENERAL);
			mPos = mData;
			memcpy(mData, contents.data(), mSize);
			mEnd = mData + mSize;
		}
		else
		{
			mData = ENN_ALLOC_T(uchar, mSize, MEMCATEGORY_GENERAL);
			mPos = mData;
			mEnd = mData + sourceStream.read(mData, mSize);
			mFreeOnClose = freeOnClose;
		}
		ENN_ASSERT(mEnd >= mPos);
	}
	//-----------------------------------------------------------------------
	MemoryDataStream::MemoryDataStream(DataStreamPtr& sourceStream, 
		bool freeOnClose, bool readOnly)
		: DataStream(static_cast<uint16>(readOnly ? READ : (READ | WRITE)))
	{
		// Copy data from incoming stream
		mSize = sourceStream->size();
		if (mSize == 0 && !sourceStream->eof())
		{
			// size of source is unknown, read all of it into memory
			String contents = sourceStream->getAsString();
			mSize = contents.size();
			mData = ENN_ALLOC_T(uchar, mSize, MEMCATEGORY_GENERAL);
			mPos = mData;
			memcpy(mData, contents.data(), mSize);
			mEnd = mData + mSize;
		}
		else
		{
			mData = ENN_ALLOC_T(uchar, mSize, MEMCATEGORY_GENERAL);
			mPos = mData;
			mEnd = mData + sourceStream->read(mData, mSize);
			mFreeOnClose = freeOnClose;
		}
		ENN_ASSERT(mEnd >= mPos);
	}
	//-----------------------------------------------------------------------
	MemoryDataStream::MemoryDataStream(const String& name, DataStream& sourceStream, 
		bool freeOnClose, bool readOnly)
		: DataStream(name, static_cast<uint16>(readOnly ? READ : (READ | WRITE)))
	{
		// Copy data from incoming stream
		mSize = sourceStream.size();
		if (mSize == 0 && !sourceStream.eof())
		{
			// size of source is unknown, read all of it into memory
			String contents = sourceStream.getAsString();
			mSize = contents.size();
			mData = ENN_ALLOC_T(uchar, mSize, MEMCATEGORY_GENERAL);
			mPos = mData;
			memcpy(mData, contents.data(), mSize);
			mEnd = mData + mSize;
		}
		else
		{
			mData = ENN_ALLOC_T(uchar, mSize, MEMCATEGORY_GENERAL);
			mPos = mData;
			mEnd = mData + sourceStream.read(mData, mSize);
			mFreeOnClose = freeOnClose;
		}
		ENN_ASSERT(mEnd >= mPos);
	}
	//-----------------------------------------------------------------------
	MemoryDataStream::MemoryDataStream(const String& name, const DataStreamPtr& sourceStream, 
		bool freeOnClose, bool readOnly)
		: DataStream(name, static_cast<uint16>(readOnly ? READ : (READ | WRITE)))
	{
		// Copy data from incoming stream
		mSize = sourceStream->size();
		if (mSize == 0 && !sourceStream->eof())
		{
			// size of source is unknown, read all of it into memory
			String contents = const_cast<DataStreamPtr&>(sourceStream)->getAsString();
			mSize = contents.size();
			mData = ENN_ALLOC_T(uchar, mSize, MEMCATEGORY_GENERAL);
			mPos = mData;
			memcpy(mData, contents.data(), mSize);
			mEnd = mData + mSize;
		}
		else
		{
			mData = ENN_ALLOC_T(uchar, mSize, MEMCATEGORY_GENERAL);
			mPos = mData;
			mEnd = mData + const_cast<DataStreamPtr&>(sourceStream)->read(mData, mSize);
			mFreeOnClose = freeOnClose;
		}
		ENN_ASSERT(mEnd >= mPos);
	}
	//-----------------------------------------------------------------------
	MemoryDataStream::MemoryDataStream(size_t inSize, bool freeOnClose, bool readOnly)
		: DataStream(static_cast<uint16>(readOnly ? READ : (READ | WRITE)))
	{
		mSize = inSize;
		mFreeOnClose = freeOnClose;
		mData = ENN_ALLOC_T(uchar, mSize, MEMCATEGORY_GENERAL);
		mPos = mData;
		mEnd = mData + mSize;
		ENN_ASSERT(mEnd >= mPos);
	}
	//-----------------------------------------------------------------------
	MemoryDataStream::MemoryDataStream(const String& name, size_t inSize, 
		bool freeOnClose, bool readOnly)
		: DataStream(name, static_cast<uint16>(readOnly ? READ : (READ | WRITE)))
	{
		mSize = inSize;
		mFreeOnClose = freeOnClose;
		mData = ENN_ALLOC_T(uchar, mSize, MEMCATEGORY_GENERAL);
		mPos = mData;
		mEnd = mData + mSize;
		ENN_ASSERT(mEnd >= mPos);
	}
	//-----------------------------------------------------------------------
	MemoryDataStream::~MemoryDataStream()
	{
		close();
	}
	//-----------------------------------------------------------------------
	size_t MemoryDataStream::read(void* buf, size_t count)
	{
		size_t cnt = count;
		// Read over end of memory?
		if (mPos + cnt > mEnd)
			cnt = mEnd - mPos;
		if (cnt == 0)
			return 0;

		ENN_ASSERT (cnt<=count);

		memcpy(buf, mPos, cnt);
		mPos += cnt;
		return cnt;
	}
	//---------------------------------------------------------------------
	size_t MemoryDataStream::write(const void* buf, size_t count)
	{
		size_t written = 0;
		if (isWriteable())
		{
			written = count;
			// we only allow writing within the extents of allocated memory
			// check for buffer overrun & disallow
			if (mPos + written > mEnd)
				written = mEnd - mPos;
			if (written == 0)
				return 0;

			memcpy(mPos, buf, written);
			mPos += written;
		}
		return written;
	}
	//-----------------------------------------------------------------------
	size_t MemoryDataStream::readLine(char* buf, size_t maxCount, 
		const String& delim)
	{
		// Deal with both Unix & Windows LFs
		bool trimCR = false;
		if (delim.find_first_of('\n') != String::npos)
		{
			trimCR = true;
		}

		size_t pos = 0;

		// Make sure pos can never go past the end of the data 
		while (pos < maxCount && mPos < mEnd)
		{
			if (delim.find(*mPos) != String::npos)
			{
				// Trim off trailing CR if this was a CR/LF entry
				if (trimCR && pos && buf[pos-1] == '\r')
				{
					// terminate 1 character early
					--pos;
				}

				// Found terminator, skip and break out
				++mPos;
				break;
			}

			buf[pos++] = *mPos++;
		}

		// terminate
		buf[pos] = '\0';

		return pos;
	}
	//-----------------------------------------------------------------------
	size_t MemoryDataStream::skipLine(const String& delim)
	{
		size_t pos = 0;

		// Make sure pos can never go past the end of the data 
		while (mPos < mEnd)
		{
			++pos;
			if (delim.find(*mPos++) != String::npos)
			{
				// Found terminator, break out
				break;
			}
		}

		return pos;

	}
	//-----------------------------------------------------------------------
	void MemoryDataStream::skip(long count)
	{
		size_t newpos = (size_t)( ( mPos - mData ) + count );
		ENN_ASSERT( mData + newpos <= mEnd );        

		mPos = mData + newpos;
	}
	//-----------------------------------------------------------------------
	void MemoryDataStream::seek( size_t pos )
	{
		ENN_ASSERT( mData + pos <= mEnd );
		mPos = mData + pos;
	}
	//-----------------------------------------------------------------------
	size_t MemoryDataStream::tell(void) const
	{
		//mData is start, mPos is current location
		return mPos - mData;
	}
	//-----------------------------------------------------------------------
	bool MemoryDataStream::eof(void) const
	{
		return mPos >= mEnd;
	}
	//-----------------------------------------------------------------------
	void MemoryDataStream::close(void)    
	{
		if (mFreeOnClose && mData)
		{
			ENN_FREE(mData, MEMCATEGORY_GENERAL);
			mData = 0;
		}

	}
	
	//-----------------------------------------------------------------------
	//-----------------------------------------------------------------------
	FileHandleDataStream::FileHandleDataStream(FILE* handle, uint16 accessMode)
		: DataStream(accessMode), mFileHandle(handle)
	{
		// Determine size
		fseek(mFileHandle, 0, SEEK_END);
		mSize = ftell(mFileHandle);
		fseek(mFileHandle, 0, SEEK_SET);
	}
	//-----------------------------------------------------------------------
	FileHandleDataStream::FileHandleDataStream(const String& name, FILE* handle, uint16 accessMode)
		: DataStream(name, accessMode), mFileHandle(handle)
	{
		// Determine size
		fseek(mFileHandle, 0, SEEK_END);
		mSize = ftell(mFileHandle);
		fseek(mFileHandle, 0, SEEK_SET);
	}
	//-----------------------------------------------------------------------
	FileHandleDataStream::~FileHandleDataStream()
	{
		close();
	}
	//-----------------------------------------------------------------------
	size_t FileHandleDataStream::read(void* buf, size_t count)
	{
		return fread(buf, 1, count, mFileHandle);
	}
	//-----------------------------------------------------------------------
	size_t FileHandleDataStream::write(const void* buf, size_t count)
	{
		if (!isWriteable())
			return 0;
		else
			return fwrite(buf, 1, count, mFileHandle);
	}
	//---------------------------------------------------------------------
	//-----------------------------------------------------------------------
	void FileHandleDataStream::skip(long count)
	{
		fseek(mFileHandle, count, SEEK_CUR);
	}
	//-----------------------------------------------------------------------
	void FileHandleDataStream::seek( size_t pos )
	{
		fseek(mFileHandle, static_cast<long>(pos), SEEK_SET);
	}
	//-----------------------------------------------------------------------
	size_t FileHandleDataStream::tell(void) const
	{
		return ftell( mFileHandle );
	}
	//-----------------------------------------------------------------------
	bool FileHandleDataStream::eof(void) const
	{
		return feof(mFileHandle) != 0;
	}
	//-----------------------------------------------------------------------
	void FileHandleDataStream::close(void)
	{
		if (mFileHandle != 0)
		{
			fclose(mFileHandle);
			mFileHandle = 0;
		}
	}
	//-----------------------------------------------------------------------
}